﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Threading;
using LogNaga;
using LogNaga.Logs;
using System.Diagnostics;
using LogNaga.Configuration;

namespace LogNaga
{
	/// <summary>
	/// 使用SqlServer的BuldCopy将异常记录到数据库中
	/// </summary>
	public class CachedSqlServerBulkCopyLog : FormattedLog, ITransformable
	{
		private static CachedSqlServerBulkCopyLog instance;
		private long isWriting;		//1表示正在写入，0表示空闲
		private DateTime nextWriteTime = DateTime.Now;	//下次写入数据库的时间
		private static readonly object SyncRoot = new object();
		private bool hasWrite;

		private Action<List<Error>> transformWrite;

		/// <summary>
		/// 私有构造函数
		/// </summary>
		private CachedSqlServerBulkCopyLog() { }

		[Conditional("DEBUG")]
		private void Trace(string s)
		{
			Console.WriteLine("::" + Thread.CurrentThread.Name + ":: " + s);
		}

		/// <summary>
		/// 获取<see cref="CachedSqlServerBulkCopyLog"/>单例
		/// </summary>
		public static CachedSqlServerBulkCopyLog Instance
		{
			get
			{
				if (instance == null)
				{
					lock (SyncRoot)
					{
						if (instance == null)
						{
							instance = new CachedSqlServerBulkCopyLog();
						}
					}
				}

				return instance;
			}
		}

		/// <summary>
		/// 将错误内容写入到数据库
		/// </summary>
		/// <param name="errorEntry"><see cref="Error"/>实例</param>
		public sealed override void WriteToDestination(Error errorEntry)
		{
			CachedQueue.Instance.Enqueue(errorEntry);

			//检查是否需要做更新操作
			if (DateTime.Now > this.nextWriteTime)
			{
				if (Interlocked.Read(ref isWriting) == 1L)
				{
					Trace("isWriting: " + hasWrite.ToString());
					return;
				}

				Trace("[ iswriting ]=" + Interlocked.Read(ref isWriting).ToString());

				Interlocked.CompareExchange(ref isWriting, 1, 0);

				Trace("change[ iswriting ] to 1 >>>> " + Interlocked.Read(ref isWriting).ToString());

				//写入db
				WriteToDb();

				EndWriteToDb();
			}
		}

		private void EndWriteToDb()
		{
			//更换下次的检查时间
			//nextWriteTime = DateTime.Now.AddMinutes(1);

			//for test
			nextWriteTime = DateTime.Now.AddSeconds(10);
			Trace("next write to db reset to: " + nextWriteTime.ToString());

			Interlocked.CompareExchange(ref isWriting, 0, 1);
			Trace("change[ iswriting ] : " + Interlocked.Read(ref isWriting).ToString() + Environment.NewLine);
		}

		/// <summary>
		/// 将内容写入到DB里
		/// </summary>
		private void WriteToDb()
		{
			//先将内容写入到cache里,每隔N时间后将cache的内容写入到DB中
			//SqlBulkCopy bulkCopy = new SqlBulkCopy(System.Configuration.ConfigurationManager.ConnectionStrings["ErrorLogging"].ConnectionString);
			SqlBulkCopy bulkCopy = new SqlBulkCopy(System.Configuration.ConfigurationManager.ConnectionStrings[LoggingConfigurationSection.GetConfig().ConnectionStringName].ConnectionString);

			//获取队列的长度
			var count = GetCachedCount();
			if (count < 1)
				return;

			Error tmpError;
			List<Error> tmpErrorList = new List<Error>(count);

			#region 构造DataTable

			DataTable dt = new DataTable();

			var ErrorDataColumn = new []{
				new DataColumn	//Id
				{
					DataType = typeof(Int32),
					ColumnName = "Id",
					AutoIncrement = true,
					Caption = "Id",
					ReadOnly = true,
					Unique = true
				},
				new DataColumn	//CreatedTime
				{
					DataType = typeof(DateTime),
					ColumnName = "CreatedTime",
					AutoIncrement = false,
					Caption = "CreatedTime",
					ReadOnly = false,
					Unique = false
				},
				new DataColumn	//ApplicationName
				{
					DataType = typeof(String),
					ColumnName = "ApplicationName",
					AutoIncrement = false,
					Caption = "ApplicationName",
					ReadOnly = false,
					Unique = false
				},
				new DataColumn	//MachineName
				{
					DataType = typeof(String),
					ColumnName = "MachineName",
					AutoIncrement = false,
					Caption = "MachineName",
					ReadOnly = false,
					Unique = false
				},
				new DataColumn	//ExceptionSource
				{
					DataType = typeof(String),
					ColumnName = "ExceptionSource",
					AutoIncrement = false,
					Caption = "ExceptionSource",
					ReadOnly = false,
					Unique = false
				},
				new DataColumn	//ExceptionMessage
				{
					DataType = typeof(String),
					ColumnName = "ExceptionMessage",
					AutoIncrement = false,
					Caption = "ExceptionMessage",
					ReadOnly = false,
					Unique = false
				},
				new DataColumn	//ExceptionStackTrace
				{
					DataType = typeof(String),
					ColumnName = "ExceptionStackTrace",
					AutoIncrement = false,
					Caption = "ExceptionStackTrace",
					ReadOnly = false,
					Unique = false
				},
				new DataColumn	//LogLevel
				{
					DataType = typeof(Int16),
					ColumnName = "LogLevel",
					AutoIncrement = false,
					Caption = "LogLevel",
					ReadOnly = false,
					Unique = false
				}
			};

			dt.Columns.AddRange(ErrorDataColumn);

			#endregion

			#region 将队列中的项填充到DataTable里

			for (var i = 0; i < count; i++)
			{
				tmpError = CachedQueue.Instance.Dequeue();
				if (tmpError == null) continue;

				var row = dt.NewRow();
				row["CreatedTime"] = tmpError.CreatedTime;
				row["ApplicationName"] = tmpError.ApplicationName;
				row["MachineName"] = tmpError.MachineName;
				row["ExceptionSource"] = tmpError.Exception.Source;
				row["ExceptionMessage"] = tmpError.Exception.Message;
				row["ExceptionStackTrace"] = tmpError.Exception.StackTrace;
				row["LogLevel"] = tmpError.LogLevel;

				dt.Rows.Add(row);
				tmpErrorList.Add(tmpError);
			}

			#endregion

			//要灌入输入的表名称为dbo.ErrorLog
			bulkCopy.DestinationTableName = "dbo.ErrorLog";
			try
			{
				bulkCopy.WriteToServer(dt);
				if (!hasWrite)
				{
					lock (SyncRoot)
					{
						hasWrite = true;
						Trace("<<<<< lock -> true >>>>>");
					}
				}
			}
			catch
			{
				if (hasWrite)
				{
					lock (SyncRoot)
					{
						hasWrite = false;
						Trace("<<<<< lock -> turn to false >>>>>");
					}
				}

				//如果出现异常需要解除锁
				EndWriteToDb();
				
				//调用备用的Log将错误转写
				this.transformWrite(tmpErrorList);

				//重新抛出
				throw;
			}
			finally
			{
				dt.Dispose();
			}
		}

		/// <summary>
		/// 获取缓存中的错误队列
		/// </summary>
		/// <returns></returns>
		public int GetCachedCount()
		{
			return CachedQueue.Instance.GetCurrentCount();
		}

		#region ITransformable Members

		/// <summary>
		/// 将Exception模式的Error转换为StringMessage模式的Error
		/// </summary>
		/// <param name="source"><see cref="Error"/>实例</param>
		/// <returns></returns>
		public Error Transform(Error source)
		{
			if (source == null)
				throw new ArgumentNullException("source");
			if (source.Exception == null)
				throw new ArgumentNullException("source");

			//source.ErrorMessage = source.Exception.ToString();
			source.ErrorMessage = ExceptionFormatterHelper.Default(source.Exception);
			return source;
		}

		public void SetTransformWrite(Action<List<Error>> transformWrite)
		{
			this.transformWrite = transformWrite;
		}

		public bool GetWriteState()
		{
			return hasWrite;
		}

		#endregion
	}
}
